<?php


namespace Yond\LaravelDomain\Models\Concerns;

use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;

/**
 * Trait HasSlug
 * @mixin Model
 */
trait HasSlug
{
    protected $slugOptions = [
        'slugFrom' => 'name',
        'slugField' => 'slug',
        'preventOverwrite' => false,
        'generateUniqueSlugs' => true,
        'maximumLength' => 100,
        'extraScopeCallback' => false,
    ];

    public static function bootHasSlug()
    {
        static::saving(function (Model $model) {
            /** @var self $model */
            $model->generateSlug();
        });
    }

    public function generateSlug(): void
    {
        if ($this->slugOptions['preventOverwrite']) {
            if ($this->{$this->slugOptions['slugField']} !== null) {
                return;
            }
        }

        $this->addSlug();
    }

    protected function addSlug()
    {
        $slug = $this->generateNonUniqueSlug();

        if ($this->slugOptions['generateUniqueSlugs']) {
            $slug = $this->makeSlugUnique($slug);
        }

        $slugField = $this->slugOptions['slugField'];

        $this->$slugField = $slug;
    }

    protected function generateNonUniqueSlug(): string
    {
        $slugField = $this->slugOptions['slugField'];

        if ($this->hasCustomSlugBeenUsed() && ! empty($this->$slugField)) {
            return $this->$slugField;
        }

        return $this->getSlugSourceString();
    }

    protected function hasCustomSlugBeenUsed(): bool
    {
        $slugField = $this->slugOptions['slugField'];

        return $this->getOriginal($slugField) != $this->$slugField;
    }

    protected function getSlugSourceString(): string
    {
        if (is_callable($this->slugOptions['slugFrom'])) {
            $slugSourceString = $this->getSlugSourceStringFromCallable();

            return $this->generateSubstring($slugSourceString);
        }

        $slugSourceString = pinyin_permalink(collect($this->slugOptions['slugFrom'])
            ->map(function($fieldName) {
                return data_get($this, $fieldName, '');
            })
            ->implode('-'));

        return $this->generateSubstring($slugSourceString);
    }

    protected function getSlugSourceStringFromCallable(): string
    {
        return call_user_func($this->slugOptions['slugFrom'], $this);
    }

    protected function makeSlugUnique(string $slug): string
    {
        $originalSlug = $slug;
        $i = 1;

        while ($this->otherRecordExistsWithSlug($slug) || $slug === '') {
            $slug = $originalSlug.'-'.$i++;
        }

        return $slug;
    }

    protected function otherRecordExistsWithSlug(string $slug): bool
    {
        $query = static::where($this->slugOptions['slugField'], $slug)
            ->withoutGlobalScopes();

        if ($this->slugOptions['extraScopeCallback']) {
            $query->where($this->slugOptions['extraScopeCallback']);
        }

        if ($this->exists) {
            $query->where($this->getKeyName(), '!=', $this->getKey());
        }

        if ($this->usesSoftDeletes()) {
            $query->withTrashed();
        }

        return $query->exists();
    }

    protected function usesSoftDeletes(): bool
    {
        return in_array('Illuminate\Database\Eloquent\SoftDeletes', class_uses($this), true);
    }

    protected function generateSubstring($slugSourceString)
    {
        if (function_exists('mb_substr')) {
            return mb_substr($slugSourceString, 0, $this->slugOptions['maximumLength']);
        }

        return substr($slugSourceString, 0, $this->slugOptions['maximumLength']);
    }

    public function scopeWhereSlug(Builder $query, string $slug)
    {
        return $query->where("slug", $slug);
    }
}
